iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0

基本流程控制可以分為 if-else 表達式和迴圈兩種。

if-else 表達式

if-else 表達式是程式邏輯不可欠缺的一部分,會根據條件不同決定要執行的程式區塊,也讓我們的程式碼產生了分支。

和其他程式語言差不多,關鍵字就3種,ifelse ifelse,後面接大括號隔出區塊。

if number > 0 {
    println!("The number is positive.");
}

if number > 0 {
    println!("The number is positive.");
} else if number % 2 == 0 {
    println!("The number is even.");
}

if number > 0 {
    println!("The number is positive.");
} else {
    println!("The number is not positive");
}

if number > 0 {
    println!("The number is positive.");
} else if number % 2 == 0 {
    println!("The number is even.");
} else {
    println!("The number is neither positive nor even.");
}

以上是一組 if-else 結構的各種變體,一組 if-else 是一個獨立的邏輯單元,最多只會執行結構內的一個邏輯區塊,與其他結構互不影響。

if 可以單獨使用,如果條件不成立就不會執行大括號內的區塊,else ifelse 都只能接在 if 後面, else if 允許鏈接多個else if 判斷不同條件,一組 if else 結構判斷是有順序的,寫在前面的會先判斷,如果前面已經符合條件就不會再去判斷和執行後面的條件。而else一定放在最後面,是一個如果前面的條件都不符合的預設選項。

需要注意的是,如布林值的介紹提到,if-else 的條件永遠要是布林值,不然會報錯,不像 JavaScript 會自動幫我們轉。

和其他程式語言一樣,Rust 也有在賦值的時候用 if-else 的寫法。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("數字結果為:{number}");
}

這種寫法會限制 if-else 的回傳型別要是一樣的,如果不同編譯會報錯。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { "六" };

    println!("數字結果為:{number}");
}
$ cargo run
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:3:44
  |
3 |     let number = if condition { 5 } else { "六" };
  |                                 -          ^^^^ expected integer, found `&str`
  |                                 |
  |                                 expected because of this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error

在這邊還是感慨一下 Rust 的 error message 真的是滿簡單明瞭的,錯誤的位置和原因都很清楚。

迴圈

Rust 裡的迴圈有 3 種: loopwhile 和 for

loop

loop 的迴圈內的程式碼會不斷執行直到明確告訴他要停下來,在 terminal 的層級就是用ctrl-c 快捷鍵,在程式內就是要用 break 關鍵字

fn main() {
    let mut counter = 0;

    loop {
        counter += 1;
        println!("第{counter}次執行");
        if counter == 10 {
            break;
        }
    };

    println!("執行總次數:{counter}");
}
$ cargo run
第1次執行
第2次執行
第3次執行
第4次執行
第5次執行
第6次執行
第7次執行
第8次執行
第9次執行
第10次執行
執行總次數:10

我們也可以把 loop 的結果賦值給變數,把要回傳的值放在 break 後面,這是 loop 特有的行為,其他兩種不能這樣用,也是 Rust 特有的行為。

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2; // counter * 2
        }
    };

    println!("結果為:{result}");
}

迴圈標籤

如果沒有特別設定,在迴圈裡面還有迴圈的時候,break只會作用在同一層的迴圈,不會作用到外層的。

fn main() {
    let mut count = 0;
    loop {
        println!("Outer loop");
        println!("count = {count}");

        loop {
            println!("Inner loop");
            println!("count = {count}");
            if count > 3 {
                println!("Inner loop break");
                break;
            }

            count += 1;
        }

        if count % 5 == 0 {
            println!("Outer loop break");
            break;
        }

        count += 1;
    }
    println!("End count = {count}");
}
$ cargo run
Outer loop
count = 0
Inner loop
count = 0
Inner loop
count = 1
Inner loop
count = 2
Inner loop
count = 3
Inner loop
count = 4
Inner loop break
Outer loop
count = 5
Inner loop
count = 5
Inner loop break
Outer loop break
End count = 5

第一次進入內層迴圈後,重複執行到達成內部迴圈終止的條件 count = 4 ,內部迴圈 break 但外部迴圈 count += 1 後還是會繼續,這時 count = 5 所以再次進入內部迴圈,內部迴圈第二次 break,然後外部迴圈也達成中止條件,整個迴圈的巢狀結構才結束。

在 Rust 裡面可以幫迴圈加上標籤,在 break 的時候可以指定終止特定標籤的迴圈

標籤寫在 loop 前面,用一個單引號標記,break 指定也是在後面接著單引好的關鍵字。

fn main() {
    let mut count = 0;
    'outer: loop {
        println!("Outer loop");
        println!("count = {count}");

        loop {
            println!("Inner loop");
            println!("count = {count}");
            if count > 3 {
                println!("Inner loop break");
                break 'outer;
            }

            count += 1;
        }

        if count % 5 == 0 {
            println!("Outer loop break");
            break;
        }

        count += 1;
    }
    println!("End count = {count}");
}
$ cargo run
  --> src/main.rs:18:9
   |
7  | /         loop {
8  | |             println!("Inner loop");
9  | |             println!("count = {count}");
10 | |             if count > 3 {
...  |
15 | |             count += 1;
16 | |         }
   | |_________- any code following this expression is unreachable
17 |
18 | /         if count % 5 == 0 {
19 | |             println!("Outer loop break");
20 | |             break;
21 | |         }
   | |_________^ unreachable statement
   |
   = note: `#[warn(unreachable_code)]` on by default

Outer loop
count = 0
Inner loop
count = 0
Inner loop
count = 1
Inner loop
count = 2
Inner loop
count = 3
Inner loop
count = 4
Inner loop break
End count = 4

以結果來說,第一次 Inner loop break 就把外層 loop 中止了,整個巢狀迴圈也結束,所以印出最後一句 End count = 4。同時也可以看到 Rust 編譯器針對外層迴圈後半的程式碼永遠不會執行到的部分會有 warning。

while

while 可以當成有特定模式的 loop ,後面會接一個條件,當這個條件為 true 的時候迴圈才會繼續執行,不然就會結束。這種模式也可以直接用 loopifelse 和 break 實作,不過用 while 可以減少 ifelse 和 break 的巢狀結構,讓程式碼更好閱讀。

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("升空!!!");
}

whilebreak 如前述,不能回傳值,編譯器一樣會給清楚的 Error。

fn main() {
    let mut number = 3;

    let result = while number != 0 {
        println!("{number}!");

        number -= 1;
        if number % 2 == 0 {
            break number;
        }
    };
    println!("升空!!!");
}
$ cargo run
error[E0571]: `break` with value from a `while` loop
 --> src/main.rs:9:13
  |
4 |     let result = while number != 0 {
  |                  ----------------- you can't `break` with a value in a `while` loop
...
9 |             break number;
  |             ^^^^^^^^^^^^ can only break with a value inside `loop` or breakable block
  |
help: use `break` on its own without a value inside this `while` loop
  |
9 |             break;
  |             ~~~~~

標籤的部分和 loop 一樣而且不同的迴圈寫法是可以混用的。

fn main() {
    let mut count = 0;
    'outer: while count < 5 {
        println!("Outer while");
        println!("count = {count}");

        loop {
            println!("Inner while");
            println!("count = {count}");
            if count > 3 {
                println!("Inner while break");
                break 'outer;
            }

            count += 1;
        }
    }
    println!("End count = {count}");
}
$ cargo run
Outer while
count = 0
Inner loop
count = 0
Inner loop
count = 1
Inner loop
count = 2
Inner loop
count = 3
Inner loop
count = 4
Inner loop break
End count = 4

有時候我們迴圈執行的次數和某個集合相關,例如遍歷集合的情況。

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("數值為:{}", a[index]);

        index += 1;
    }
}

這樣就可以遍歷 a 陣列,不過如果沒有處理好索引值邊界容易造成程式錯誤(panicked)。

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 6 {
        println!("數值為:{}", a[index]);

        index += 1;
    }
}
$ cargo run
數值為:10
數值為:20
數值為:30
數值為:40
數值為:50
thread 'main' panicked at src/main.rs:6:32:
index out of bounds: the len is 5 but the index is 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

這同時也會讓程式變慢,因為編譯器得在執行時的程式碼對迴圈中每次疊代中進行索引是否在陣列範圍內的條件檢查。

這種情況就很適合用 for 處理。

for

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("數值為:{element}");
    }
}

標籤的用法也可以在 loop

fn main() {
    let mut count = 0;

    'outer: for i in 1..=5 {
        for j in 1..=5 {
            println!("i = {}, j = {}", i, j);
            count += 1;

            if count == 8 {
                println!("達到中止條件,退出最外層迴圈");
                break 'outer;
            }
        }
    }

    println!("最外層迴圈已結束");
}
$ cargo
i = 1, j = 1
i = 1, j = 2
i = 1, j = 3
i = 1, j = 4
i = 1, j = 5
i = 2, j = 1
i = 2, j = 2
i = 2, j = 3
達到中止條件,退出最外層迴圈
最外層迴圈已結束

1..=5 是 Rust 中用來表示範圍的一種語法,稱為「包含範圍」(inclusive range),它包括兩端點,表示從 1 到 5 的所有整數。

標籤的用法在某些複雜的場景中特別有用,例如遍歷多維陣列時。

假設我們處理一個二維陣列,並希望在找到特定條件滿足的元素時立即結束整個搜尋,而不只是當前的內層迴圈。

fn main() {
    let matrix = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
        vec![7, 8, 9],
    ];

    let target = 5;
    'outer: for row in &matrix {
        for &item in row {
            if item == target {
                println!("找到目標數字:{item}");
                break 'outer;
            }
        }
    }
    println!("搜尋結束");
}

在這個例子中,當找到目標數字時,標籤 break 會立即跳出外層的 for 迴圈,而不會繼續搜尋。這樣的用法在大型資料集或多層結構中,可以有效節省計算資源並提升程式效能。

總結

loop 是最基礎也最彈性的迴圈,適用於不確定迴圈次數、需要動態中止條件的情境。特別適合無限循環或需根據內部邏輯決定何時結束的情況。

while 適用在明確知道迴圈結束條件的情況,相比 loopwhile 結構更簡潔,減少了巢狀結構。

for 則是一種最安全、簡潔的寫法,因為他限制了執行的次數上限,並且避免了像是讀取無效索引值造成程式錯誤的風險。是需要遍歷一個範圍或集合中的所有元素時的首選,適用在明確知道執行次數上限的情況,官方也建議大部分的情況盡可能的用 for 來表達。


上一篇
Day9 - 變數遮蔽
下一篇
Day11 - 函數
系列文
螃蟹幼幼班:Rust 入門指南25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言